package cn.facetoken.homer;

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.nnf.nnfkitlib.NNF_FaceInfo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.annotation.Nonnull;

import cn.openbiotoken.BiometricsFactory;
import cn.openbiotoken.BiometricsFeature;
import cn.openbiotoken.BiometricsFeatureExtractor;
import cn.openbiotoken.android.facetoken.HomoSapiensEyeprintBitmapBiometricsFactory;
import cn.openbiotoken.android.facetoken.HomoSapiensFaceBitmapBiometricsFactory;
import cn.openbiotoken.impl.BiometricsFeatureImpl;

import static android.graphics.PixelFormat.A_8;
import static android.graphics.PixelFormat.JPEG;
import static android.graphics.PixelFormat.LA_88;
import static android.graphics.PixelFormat.L_8;
import static android.graphics.PixelFormat.RGBA_1010102;
import static android.graphics.PixelFormat.RGBA_4444;
import static android.graphics.PixelFormat.RGBA_5551;
import static android.graphics.PixelFormat.RGBA_8888;
import static android.graphics.PixelFormat.RGBA_F16;
import static android.graphics.PixelFormat.RGBX_8888;
import static android.graphics.PixelFormat.RGB_332;
import static android.graphics.PixelFormat.RGB_565;
import static android.graphics.PixelFormat.RGB_888;
import static android.graphics.PixelFormat.TRANSLUCENT;
import static android.graphics.PixelFormat.TRANSPARENT;
import static android.graphics.PixelFormat.UNKNOWN;
import static android.graphics.PixelFormat.YCbCr_420_SP;
import static android.graphics.PixelFormat.YCbCr_422_I;
import static android.graphics.PixelFormat.YCbCr_422_SP;
import static java.lang.String.format;

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {

    public CameraPreview(@Nonnull final Fragment fragment, @Nonnull final Camera camera) {
        super(fragment.requireContext());
        this.fragment = fragment;
        this.camera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = getHolder();
        Log.d(getClass().getSimpleName(), String.format("constructor(): Surface Holder %s was got from Surface View.", holder));
        holder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        //holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        then = System.currentTimeMillis();
    }

    private @Nonnull final Fragment fragment;
    private @Nonnull final Camera camera;
    private @Nullable final SurfaceHolder holder;
    private long then = 0L;

    @Override
    public void surfaceCreated(@NonNull final SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        Log.d(getClass().getSimpleName(), "surfaceCreated(): Surface holder was passed in.");

        try {
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Setting preview display for camera.");
            camera.setPreviewDisplay(holder);
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Succeeded to set preview display for camera.");

            Log.d(getClass().getSimpleName(), "surfaceCreated(): Starting preview camera.");
            camera.startPreview();
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Succeeded to start preview for camera.");
        } catch (final IOException ignored) {
            releaseCamera();
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Failed to create camera surface view", ignored);
        }
    }

    @Override
    public void surfaceChanged(@NonNull final SurfaceHolder holder, final int format, final int width, final int height) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        Log.d(getClass().getSimpleName(), "surfaceChanged(): Surface holder was got.");
        Log.d(getClass().getSimpleName(), format("surfaceChanged(): Format = %d, %s.", format, formatToString(format)));
        Log.d(getClass().getSimpleName(), format("surfaceChanged(): width = %d, height = %d.", width, height));

        if (null == holder.getSurface()) {
            // preview surface does not exist
            Log.d(getClass().getSimpleName(), "surfaceChanged(): holder.surface is null. Nothing happened...");
            return;
        }

        // stop preview before making changes
        try {
            camera.stopPreview();
        } catch (final Exception ignored) {
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Failed to stop preview before making changes.", ignored);
            // ignore: tried to stop a non-existent preview
        }

        final Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(width, height);

        //横竖屏镜头自动调整
        if (Configuration.ORIENTATION_LANDSCAPE!=getResources().getConfiguration().orientation) {
            parameters.set("orientation", "portrait");
            parameters.set("rotation", 90); // 镜头角度转90度（默认摄像头是横拍）
            camera.setDisplayOrientation(90); // 在2.2以上可以使用
        }
        else// 如果是横屏
        {
            parameters.set("orientation", "landscape");
            camera.setDisplayOrientation(0); // 在2.2以上可以使用
        }
        camera.setPreviewCallback(this);
        camera.setParameters(parameters);

        // set preview size and make any resize, rotate or
        // reformatting changes here
        //camera.setDisplayOrientation(90);

        // start preview with new settings
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } catch (final Exception ignored) {
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Failed to start camera preview.", ignored);
        }

        // 必须使用来自 getSupportedPreviewSizes() 的值。请勿在 setPreviewSize() 方法中设置任意值。
        // Android 7.0（API 级别 24）及更高版本引入多窗口功能，无法再假设预览的宽高比与 Activity 相同，
        // 即便调用 setDisplayOrientation() 也是如此。根据窗口尺寸和屏幕宽高比，
        // 可能需要使用 Letterbox 布局将横向的相机预览适配到纵向布局中，或反之亦然。
    }

    @Override
    public void surfaceDestroyed(@NonNull final SurfaceHolder holder) {
        // Take care of releasing the Camera preview in your activity.
        releaseCamera();
        Log.d(getClass().getSimpleName(), "surfaceDestroyed(): Surface holder was got.");
    }

    private void releaseCamera() {
        if (null!=camera) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            //camera = null;
        }
    }

    public static String formatToString(final int format) {
        switch (format) {
            case UNKNOWN:
                return "UNKNOWN";
            case TRANSLUCENT:
                return "TRANSLUCENT";
            case TRANSPARENT:
                return "TRANSPARENT";
            case RGBA_8888:
                return "RGBA_8888";
            case RGBX_8888:
                return "RGBX_8888";
            case RGB_888:
                return "RGB_888";
            case RGB_565:
                return "RGB_565";
            case RGBA_5551:
                return "RGBA_5551";
            case RGBA_4444:
                return "RGBA_4444";
            case A_8:
                return "A_8";
            case L_8:
                return "L_8";
            case LA_88:
                return "LA_88";
            case RGB_332:
                return "RGB_332";
            case YCbCr_422_SP:
                return "YCbCr_422_SP";
            case YCbCr_420_SP:
                return "YCbCr_420_SP";
            case YCbCr_422_I:
                return "YCbCr_422_I";
            case RGBA_F16:
                return "RGBA_F16";
            case RGBA_1010102:
                return "RGBA_1010102";
            //case HSV_888:
            case 0x37:
                return "HSV_888";
            case JPEG:
                return "JPEG";
            default:
                return Integer.toString(format);
        }
    }

    public static void setCameraDisplayOrientation(@NonNull final Activity activity,
                                                   final int cameraId, @NonNull final Camera camera) {
        @NonNull final Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        final int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
    }

    @Override
    public void onPreviewFrame(final byte[] data, final Camera camera) {
        long timeSpent = System.currentTimeMillis()-then;
        then = System.currentTimeMillis();
        Log.i(getClass().getSimpleName(), String.format("onPreviewFrame(): data.length = %d, time spent = %d ms.", data.length, timeSpent));

        @NonNull final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length-1);

        new Thread(
                () -> {
                    @NonNull final List<FaceSample> samples = MainApplication.getMainDatabase().faceSampleDao().loadAll();

                    @NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> eyeprintFactory = new HomoSapiensEyeprintBitmapBiometricsFactory(getContext());
                    final FaceSample topSimilarEyeprintSample = matchSample(eyeprintFactory, bitmap, samples);
                    if (null!=topSimilarEyeprintSample) {
                        // call UI
                        @NonNull final PhotoRegistration<String> photo = topSimilarEyeprintSample.toPhotoRegistration();
                        Log.i(getClass().getSimpleName(), String.format("run(): Top Similar Eyeprint: id= %d, name = %s.", photo.id, photo.name));
                        return;
                    }

                    @NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> faceFactory = new HomoSapiensFaceBitmapBiometricsFactory(getContext());
                    final FaceSample topSimilarFaceSample = matchSample(faceFactory, bitmap, samples);
                    if (null!=topSimilarFaceSample) {
                        @NonNull final PhotoRegistration<String> photo = topSimilarFaceSample.toPhotoRegistration();
                        Log.i(getClass().getSimpleName(), String.format("run(): Top Similar Face: id= %d, name = %s.", photo.id, photo.name));
                        //CameraPreview.this.fragment.requireActivity().runOnUiThread(
                        //        () -> CameraPreview.this.fragment.updateTopSample(topSimilarity, topSimilarSample, photoUri)
                        //);
                    }
                }
        ).start();
    }

    @Nullable
    public static FaceSample matchSample(@NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> factory, @NonNull final Bitmap bitmap, @NonNull final List<FaceSample> samples) {
        @NonNull final BiometricsFeatureExtractor<float[]> extractor = factory.createFeatureExtractor(bitmap);
        @NonNull final BiometricsFeature<float[]> sampleFeature = extractor.extract();
        @NonNull final List<BiometricsFeature<float[]>> library = new ArrayList<>(samples.size());
        for (FaceSample sample : samples) {
            BiometricsFeature<float[]> appFeature = new BiometricsFeatureImpl<>();
            appFeature.setFeature(sample.getFaceFeature());
            appFeature.putExtra(BiometricsFeature.EXTRA_KEY_SAMPLE_ID, sample.id);
            library.add(appFeature);
        }

        final BiometricsFeature<float[]> matched = extractor.top(sampleFeature, Collections.unmodifiableList(library), 0.6F);
        Log.d(CameraPreview.class.getSimpleName(), String.format("matchSample() Thread.run(): matched = %s.", matched));

        final float topSimilarity = null == matched ? 0.0F : matched.getFloatExtra(BiometricsFeature.EXTRA_KEY_SIMILARITY);
        Log.d(CameraPreview.class.getSimpleName(), String.format("matchSample() Thread.run(): Top Similarity = %f.", topSimilarity));
        @Nullable final FaceSample topSimilarSample = null == matched ? null : samples.stream().filter(sample -> matched.getLongExtra(BiometricsFeature.EXTRA_KEY_SAMPLE_ID) == sample.id).findFirst().orElse(null);
        return topSimilarSample;
    }
}
